身為一個軟體工程師,我們平常很常把注意力放在「怎麼把功能做出來」。
例如:
但當系統的使用者從 10 個人變成 10 萬、100 萬,甚至 1,000 萬人之後,我們面對的問題就不再只是「這個功能怎麼寫」。
而會開始變成:
如果同時有 100 萬個 Request 進來怎麼辦?
如果 Database 掛掉怎麼辦?
如果其中一台 Server 掛掉呢?
如果今天的流量突然增加 100 倍呢?
這些問題,就是我們開始進入 System Design(系統設計) 的地方。
這個系列,我希望透過 30 天的時間,從最基礎的 System Design 概念開始,一步一步建立自己設計系統的思考方式。
先從最簡單的 Web Application 開始。
假設今天我們開發了一個網站,它的架構可能長這樣:
User
↓
Frontend
↓
Backend API
↓
Database
使用者透過 Frontend 操作網站,Frontend 呼叫 Backend API,Backend 再從 Database 取得資料並回傳。
如果今天只有 10 個人在使用,這樣的架構可能完全沒有問題。
但如果我們的產品突然爆紅了呢?
10 users
↓
1,000 users
↓
100,000 users
↓
1,000,000 users
↓
10,000,000 users
原本的一台 Backend Server 可能開始處理不完 Request。
Database 可能開始變慢。
世界各地的使用者可能開始覺得圖片載入速度很慢。
甚至只要唯一的一台 Server 掛掉,整個服務就跟著消失。
因此,我們可能開始加入更多東西:
┌── Backend Server #1
User → Load Balancer├── Backend Server #2
└── Backend Server #3
↓
Cache
↓
Database
再繼續擴大,可能還會出現:
這時候問題已經不是「某一個 Function 要怎麼寫」。
而是:
我們要如何設計整個系統,讓它在使用者與資料量不斷增加的情況下,依然能夠穩定、快速、可靠地運作?
這就是我目前對 System Design 最簡單的理解。
我覺得一開始學 System Design 時,一個很重要的觀念,就是把它跟 Coding 的思考層級分開。
How do I build this feature?
例如今天要實作 Login,我們可能會思考:
POST /login
1. Receive email/password
2. Query user from database
3. Verify password
4. Generate token
5. Return response
這些問題主要關注的是:
「這個功能要怎麼實作?」
How do I build the whole system?
例如同樣是 Login:
如果今天只有 100 個 User,可能很簡單。
但如果今天變成:
1,000,000 users
我們就開始需要問:
所以我目前會把兩者簡單理解成:
Coding
↓
How do I build this feature?
System Design
↓
How do I design the whole system?
當然,System Design 最後還是要靠程式實作。
兩者並不是互相取代,而是思考的層級不同。
既然剛剛一直提到「使用者變多」,那就會遇到 System Design 裡非常重要的一個詞:
簡單來說:
當系統的 workload 增加時,系統是否有能力透過增加資源,繼續處理增加的需求。
例如原本系統需要處理:
100 requests / second
後來變成:
1,000 requests / second
甚至:
100,000 requests / second
我們希望系統不會因為流量增加就直接掛掉。
而是可以透過增加資源來應付更多的 Request。
假設原本只有:
Requests
↓
┌──────────────┐
│ Server │
│ │
│ CPU: 2 cores │
│ RAM: 4 GB │
└──────────────┘
現在 Server 不夠用了。
最直覺的方法可能是:
那我換一台更強的 Server 不就好了?
例如:
CPU: 2 cores → 16 cores
RAM: 4 GB → 64 GB
這種做法稱為:
概念非常簡單:
把同一台機器變得更強。
但問題是,一台機器不可能無限升級。
所以另外一種做法是:
┌── Server #1
Requests ────────┼── Server #2
├── Server #3
└── Server #4
不是一直升級同一台 Server,而是:
增加更多 Server。
這稱為:
這兩個概念我們之後會再深入討論。
但看到這裡,我馬上產生一個問題:
如果今天有四台 Server:
Server #1
Server #2
Server #3
Server #4
那 User 的 Request 到底要送去哪一台?
總不能讓使用者自己選吧?
這時候就需要另外一個角色幫忙分配流量:
┌── Server #1
│
User → ??? ───────┼── Server #2
│
├── Server #3
│
└── Server #4
這個 ???,就是之後會介紹到的 Load Balancer。
看到這裡可能會覺得:
那我一直增加 Server 不就好了?
事情當然沒有這麼簡單。
假設今天我們增加到 100 台 Server,但所有 Server 最後都連到同一個 Database:
Server #1 ─┐
Server #2 │
Server #3 ├──→ Database
... │
Server #100 ┘
這時候 Backend Server 可能不是 Bottleneck 了。
反而變成:
Database 扛不住。
所以 System Design 很有趣的一點就是:
解決了一個 Bottleneck,下一個 Bottleneck 可能又出現。
這也是為什麼後面的文章會慢慢介紹:
Load Balancer
Database Replication
Database Sharding
Cache
CDN
Message Queue
...
這些技術不是為了讓 Architecture Diagram 看起來很厲害,而是每一個 Component 都應該是在解決某個具體問題。
這也是我覺得學 System Design 很重要的一個觀念。
寫 LeetCode 時,我們通常會希望找到一個相對明確的 Solution。
例如:
Time Complexity: O(n)
Space Complexity: O(1)
但 System Design 通常不是:
「這就是唯一正確的 Architecture。」
而是會一直遇到選擇。
例如:
SQL or NoSQL?
Vertical Scaling or Horizontal Scaling?
Strong Consistency or Eventual Consistency?
Performance or Cost?
Monolith or Microservices?
每一個選擇都有優點,也都有缺點。
所以:
System Design is about trade-offs.
我們真正需要回答的不是:
「哪一個技術比較厲害?」
而是:
「為什麼這個 System 在這個 Requirement 下需要這個技術?」
例如今天只是做一個:
100 users
使用的小型網站。
我們可能根本不需要:
Microservices
Kafka
Redis
Kubernetes
Sharding
如果沒有需求,硬把這些東西全部放進去,反而增加:
所以好的 System Design 並不是:
「用了多少厲害的技術。」
而是:
「用了適合這個問題的技術。」
這也是我希望自己在接下來 30 天慢慢建立的能力。
Day 1 先不急著進入複雜的架構。
今天我想先建立幾個最重要的觀念:
如果要用一句話總結今天:
System Design 不是在背 Architecture,而是在學習面對不同 Requirement 時,如何做出合理的 Engineering Decision。
今天我們已經知道,一個系統最簡單可以想成:
User → Frontend → Backend → Database
但其實這張圖省略了非常多東西。
當我們在 Browser 輸入一個網址並按下 Enter:
Browser 到底怎麼知道 Server 在哪裡?
Request 又是怎麼從我們的電腦一路跑到 Backend?
Backend 回傳的資料又是怎麼回到我們的 Browser?
下一篇:
Day 2|當你在瀏覽器輸入網址後發生了什麼?從 DNS 到 Server 的 Request Journey
我們會從一個 Request 的旅程開始,畫出這個系列第一張更完整的 System Architecture。